﻿namespace EasyWeb.Test
{
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using System.Threading.Tasks;
    using EasyWeb.Configuration;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class EWFieldDefinitionDao_Test : LibraryTest
    {
        public EWEntityDefinition Entity { get; private set; }
        public EWEntityDefinition LookupSourceEntity { get; private set; }
        public EWEntityDefinition LookupSourceEntity2 { get; private set; }
        public EWSelectionDefinition Selection { get; private set; }
        public EWSelectionDefinition Selection2 { get; private set; }

        [TestMethod]
        public async Task All_Test()
        {
            var originalFields = BuildFields();
            foreach (var field in originalFields)
            {
                await this.FieldDefinitionDao.CreateAsync(field);
            }
            await ValidateDatabaseColumnsAsync(new[]
            {
                new Tuple<string, string>("testcase_InternalName0", "BIT"),
                new Tuple<string, string>("testcase_InternalName1", "UNIQUEIDENTIFIER"),
                new Tuple<string, string>("testcase_InternalName2", "MONEY"),
                new Tuple<string, string>("testcase_InternalName3", "DATETIME2"),
                new Tuple<string, string>("testcase_InternalName4", "DATETIMEOFFSET"),
                new Tuple<string, string>("testcase_filename_InternalName5", "NVARCHAR"),
                new Tuple<string, string>("testcase_filemime_InternalName5", "VARCHAR"),
                new Tuple<string, string>("testcase_filebinary_InternalName5", "VARBINARY"),
                new Tuple<string, string>("testcase_InternalName6", "UNIQUEIDENTIFIER"),
                new Tuple<string, string>("testcase_InternalName7", "NVARCHAR"),
                new Tuple<string, string>("testcase_InternalName8", "DECIMAL"),
                new Tuple<string, string>("testcase_InternalName9", "VARCHAR"),
                new Tuple<string, string>("testcase_InternalName10", "UNIQUEIDENTIFIER"),
            });

            var createdFields = await this.FieldDefinitionDao.SelectAsync();
            CompareFields(originalFields, createdFields);

            RebuildFields(createdFields);
            foreach (var field in createdFields)
            {
                await this.FieldDefinitionDao.UpdateAsync(field);
            }
            await ValidateDatabaseColumnsAsync(new[]
            {
                new Tuple<string, string>("testcase_InternalName0updated", "BIT"),
                new Tuple<string, string>("testcase_InternalName1updated", "UNIQUEIDENTIFIER"),
                new Tuple<string, string>("testcase_InternalName2updated", "MONEY"),
                new Tuple<string, string>("testcase_InternalName3updated", "DATE"),
                new Tuple<string, string>("testcase_InternalName4updated", "DATETIMEOFFSET"),
                new Tuple<string, string>("testcase_filename_InternalName5updated", "NVARCHAR"),
                new Tuple<string, string>("testcase_filemime_InternalName5updated", "VARCHAR"),
                new Tuple<string, string>("testcase_filebinary_InternalName5updated", "VARBINARY"),
                new Tuple<string, string>("testcase_InternalName6updated", "UNIQUEIDENTIFIER"),
                new Tuple<string, string>("testcase_InternalName7updated", "NVARCHAR"),
                new Tuple<string, string>("testcase_InternalName8updated", "BIGINT"),
                new Tuple<string, string>("testcase_InternalName9updated", "NVARCHAR"),
                new Tuple<string, string>("testcase_InternalName10updated", "UNIQUEIDENTIFIER"),
            });

            var updatedFields = await this.FieldDefinitionDao.SelectAsync();
            CompareFields(createdFields, updatedFields);

            foreach (var field in updatedFields)
            {
                await this.FieldDefinitionDao.DeleteAsync(field.Id, field.Timestamp);
            }

            var deletedFields = await this.FieldDefinitionDao.SelectAsync();
            Assert.AreEqual(0, deletedFields.Count());
        }

        [TestInitialize]
        public override void TestInitialize()
        {
            base.TestInitialize();

            TestInitializeSystemPrefix();

            this.Entity = CreateEntity("test");
            this.LookupSourceEntity = CreateEntity("source");
            this.LookupSourceEntity2 = CreateEntity("source2");
            this.Selection = CreateSelection("test");
            CreateSelectionItem("key1", this.Selection);
            CreateSelectionItem("key2", this.Selection);
            this.Selection2 = CreateSelection("test2");
            CreateSelectionItem("key1", this.Selection2);
            CreateSelectionItem("key2", this.Selection2);
        }

        private void TestInitializeSystemPrefix()
        {
            var config = EWHelpers.Async.RunSync(async () => await this.ConfigurationDao.SelectAsync<EWSystemPrefixConfiguration>());
            config.Value = "testcase_";
            EWHelpers.Async.RunSync(async () => await this.ConfigurationDao.UpdateAsync(config));
        }

        private EWEntityDefinition CreateEntity(string name)
        {
            var result = new EWEntityDefinition()
            {
                InternalName = name,
                DisplayName = CreateText(),
                Description = CreateText()
            };
            EWHelpers.Async.RunSync(async () => await this.EntityDefinitionDao.CreateAsync(result));

            return result;
        }

        private EWSelectionDefinition CreateSelection(string name)
        {
            var result = new EWSelectionDefinition()
            {
                InternalName = name,
                DisplayName = CreateText()
            };
            EWHelpers.Async.RunSync(async () => await this.SelectionDefinitionDao.CreateAsync(result));

            return result;
        }

        private EWSelectionItemDefinition CreateSelectionItem(string key, EWSelectionDefinition owner)
        {
            var result = new EWSelectionItemDefinition()
            {
                Key = key,
                Value = CreateText(),
                SelectionDefinitionId = owner.Id
            };
            EWHelpers.Async.RunSync(async () => await this.SelectionItemDefinitionDao.CreateAsync(result));

            return result;
        }

        private IEnumerable<EWFieldDefinition> BuildFields()
        {
            var fields = new EWFieldDefinition[]
            {
                new EWFieldDefinitionBoolean()
                {
                    DefaultValue = false,
                },
                new EWFieldDefinitionChoice()
                {
                    DefaultValue = this.Selection.SelectionItems[0].Id,
                    SelectionDefinitionId = this.Selection.Id,
                    DisplayMode = EWChoiceDisplayMode.CheckBox,
                },
                new EWFieldDefinitionCurrency()
                {
                    DefaultValue = 100M,
                    MinimumValue = 10M,
                    MaximumValue = 200M,
                },
                new EWFieldDefinitionDateTime()
                {
                    DefaultValue = new DateTime(2000, 10, 20, 22, 20, 10, 0, DateTimeKind.Unspecified),
                    Mode = EWDateTimeMode.DateTime,
                    IsNowDefaultValue = true,
                },
                new EWFieldDefinitionDateTimeOffset()
                {
                    DefaultValue = new DateTimeOffset(2000, 10, 20, 22, 20, 10, 0, new TimeSpan(0, 2, 0, 0, 0)),
                    IsNowDefaultValue = true,
                    DisplayTimeZone = true,
                    DefaultUtcOffset = new TimeSpan(0, 2, 0, 0, 0),
                },
                new EWFieldDefinitionFile()
                {
                    MinimumLength = 50L,
                    MaximumLength = 300L,
                },
                new EWFieldDefinitionLookup()
                {
                    LookupEntityId = this.LookupSourceEntity.Id,
                },
                new EWFieldDefinitionMultiLineText()
                {
                    DefaultValue = string.Format("aaa{0}bbb{0}ccc", '\n'),
                    NumberOfLines = 10,
                    OperationMode = EWTextOperationMode.AppendToEnd,
                    StoreMode = EWTextStoreMode.Ansi,
                    TextMode = EWTextMode.Xml,
                },
                new EWFieldDefinitionNumber()
                {
                    DefaultValue = 100M,
                    MinimumValue = 100M,
                    MaximumValue = 900M,
                    NumberFormat = EWNumberFormat.Float,
                    DisplayMode = EWNumberDisplayMode.Percentage,
                },
                new EWFieldDefinitionText()
                {
                    DefaultValue = "aaa",
                    MinimumLength = 1,
                    MaximumLength = 10,
                    StoreMode = EWTextStoreMode.Ansi,
                },
                new EWFieldDefinitionWho()
                {
                    AllowMultiple = true,
                    SelectionMode = EWWhoSelectionMode.UserAndGroup,
                    SelectionSource = EWWhoSelectionSource.EasyWeb,
                },
            };

            for (int i = 0; i < fields.Length; i++)
            {
                var field = fields[i];
                field.EntityDefinitionId = this.Entity.Id;
                field.InternalName = string.Format("InternalName{0}", i);
                field.DisplayName = CreateText();
                field.Description = CreateText();
                field.AllowNull = true;
                field.IsUnique = true;
            }

            return fields;
        }

        private void RebuildFields(IEnumerable<EWFieldDefinition> fields)
        {
            {
                foreach (var field in fields)
                {
                    field.InternalName += "updated";
                    field.AllowNull = !field.AllowNull;
                    field.IsUnique = !field.IsUnique;
                }
            }

            {
                var field = fields.OfType<EWFieldDefinitionBoolean>().Single();
                field.DefaultValue = !((bool)field.DefaultValue);
            }

            {
                var field = fields.OfType<EWFieldDefinitionChoice>().Single();
                field.DefaultValue = this.Selection2.SelectionItems[0].Id;
                field.SelectionDefinitionId = this.Selection2.Id;
                field.DisplayMode = EWChoiceDisplayMode.RadioButton;
            }

            {
                var field = fields.OfType<EWFieldDefinitionCurrency>().Single();
                field.DefaultValue = (decimal)field.DefaultValue + 100M;
                field.MinimumValue = field.MinimumValue + 100M;
                field.MaximumValue = field.MaximumValue + 100M;
            }

            {
                var field = fields.OfType<EWFieldDefinitionDateTime>().Single();
                field.DefaultValue = ((DateTime)field.DefaultValue).AddDays(1D);
                field.Mode = EWDateTimeMode.Date;
                field.IsNowDefaultValue = !field.IsNowDefaultValue;
            }

            {
                var field = fields.OfType<EWFieldDefinitionDateTimeOffset>().Single();
                field.DefaultValue = ((DateTimeOffset)field.DefaultValue).AddHours(1D);
                field.IsNowDefaultValue = !field.IsNowDefaultValue;
                field.DisplayTimeZone = !field.DisplayTimeZone;
                field.DefaultUtcOffset = field.DefaultUtcOffset.Value.Add(new TimeSpan(0, 1, 0, 0, 0));
            }

            {
                var field = fields.OfType<EWFieldDefinitionFile>().Single();
                field.MinimumLength = field.MinimumLength.Value + 100L;
                field.MaximumLength = field.MaximumLength + 100L;
            }

            {
                var field = fields.OfType<EWFieldDefinitionLookup>().Single();
                field.LookupEntityId = this.LookupSourceEntity2.Id;
            }

            {
                var field = fields.OfType<EWFieldDefinitionMultiLineText>().Single();
                field.DefaultValue = ((string)field.DefaultValue) + "aaa";
                field.NumberOfLines = field.NumberOfLines + 100;
                field.OperationMode = EWTextOperationMode.AppendToStart;
                field.StoreMode = EWTextStoreMode.Unicode;
                field.TextMode = EWTextMode.Xml;
            }

            {
                var field = fields.OfType<EWFieldDefinitionNumber>().Single();
                field.DefaultValue = ((decimal)field.DefaultValue) + 100M;
                field.MinimumValue = field.MinimumValue + 100M;
                field.MaximumValue = field.MaximumValue + 100M;
                field.NumberFormat = EWNumberFormat.Integer;
                field.DisplayMode = EWNumberDisplayMode.FourDecimals;
            }

            {
                var field = fields.OfType<EWFieldDefinitionText>().Single();
                field.DefaultValue = ((string)field.DefaultValue) + "aaa";
                field.MinimumLength = field.MinimumLength + 100;
                field.MaximumLength = field.MaximumLength + 100;
                field.StoreMode = EWTextStoreMode.Unicode;
            }

            {
                var field = fields.OfType<EWFieldDefinitionWho>().Single();
                field.AllowMultiple = !field.AllowMultiple;
                field.SelectionMode = EWWhoSelectionMode.User;
                field.SelectionSource = EWWhoSelectionSource.Source;
            }
        }

        private static void CompareFields(IEnumerable<EWFieldDefinition> sources, IEnumerable<EWFieldDefinition> targets)
        {
            {
                var source = sources.OfType<EWFieldDefinitionBoolean>().Single();
                var target = targets.OfType<EWFieldDefinitionBoolean>().Single();
                Assert.AreEqual(source.DefaultValue, target.DefaultValue);
            }

            {
                var source = sources.OfType<EWFieldDefinitionChoice>().Single();
                var target = targets.OfType<EWFieldDefinitionChoice>().Single();
                Assert.AreEqual(source.DefaultValue, target.DefaultValue);
                Assert.AreEqual(source.SelectionDefinitionId, target.SelectionDefinitionId);
                Assert.AreEqual(source.DisplayMode, target.DisplayMode);
            }

            {
                var source = sources.OfType<EWFieldDefinitionCurrency>().Single();
                var target = targets.OfType<EWFieldDefinitionCurrency>().Single();
                Assert.AreEqual(source.DefaultValue, target.DefaultValue);
                Assert.AreEqual(source.MinimumValue, target.MinimumValue);
                Assert.AreEqual(source.MaximumValue, target.MaximumValue);
            }

            {
                var source = sources.OfType<EWFieldDefinitionDateTime>().Single();
                var target = targets.OfType<EWFieldDefinitionDateTime>().Single();
                Assert.AreEqual(source.DefaultValue, target.DefaultValue);
                Assert.AreEqual(source.Mode, target.Mode);
                Assert.AreEqual(source.IsNowDefaultValue, target.IsNowDefaultValue);
            }

            {
                var source = sources.OfType<EWFieldDefinitionDateTimeOffset>().Single();
                var target = targets.OfType<EWFieldDefinitionDateTimeOffset>().Single();
                Assert.AreEqual(source.DefaultValue, target.DefaultValue);
                Assert.AreEqual(source.IsNowDefaultValue, target.IsNowDefaultValue);
                Assert.AreEqual(source.DisplayTimeZone, target.DisplayTimeZone);
                Assert.AreEqual(source.DefaultUtcOffset, target.DefaultUtcOffset);
            }

            {
                var source = sources.OfType<EWFieldDefinitionFile>().Single();
                var target = targets.OfType<EWFieldDefinitionFile>().Single();
                Assert.AreEqual(source.MinimumLength, target.MinimumLength);
                Assert.AreEqual(source.MaximumLength, target.MaximumLength);
            }

            {
                var source = sources.OfType<EWFieldDefinitionLookup>().Single();
                var target = targets.OfType<EWFieldDefinitionLookup>().Single();
                Assert.AreEqual(source.LookupEntityId, target.LookupEntityId);
            }

            {
                var source = sources.OfType<EWFieldDefinitionMultiLineText>().Single();
                var target = targets.OfType<EWFieldDefinitionMultiLineText>().Single();
                Assert.AreEqual(source.DefaultValue, target.DefaultValue);
                Assert.AreEqual(source.NumberOfLines, target.NumberOfLines);
                Assert.AreEqual(source.OperationMode, target.OperationMode);
                Assert.AreEqual(source.StoreMode, target.StoreMode);
                Assert.AreEqual(source.TextMode, target.TextMode);
            }

            {
                var source = sources.OfType<EWFieldDefinitionNumber>().Single();
                var target = targets.OfType<EWFieldDefinitionNumber>().Single();
                Assert.AreEqual(source.DefaultValue, target.DefaultValue);
                Assert.AreEqual(source.MinimumValue, target.MinimumValue);
                Assert.AreEqual(source.MaximumValue, target.MaximumValue);
                Assert.AreEqual(source.NumberFormat, target.NumberFormat);
                Assert.AreEqual(source.DisplayMode, target.DisplayMode);
            }

            {
                var source = sources.OfType<EWFieldDefinitionText>().Single();
                var target = targets.OfType<EWFieldDefinitionText>().Single();
                Assert.AreEqual(source.DefaultValue, target.DefaultValue);
                Assert.AreEqual(source.MinimumLength, target.MinimumLength);
                Assert.AreEqual(source.MaximumLength, target.MaximumLength);
                Assert.AreEqual(source.StoreMode, target.StoreMode);
            }

            {
                var source = sources.OfType<EWFieldDefinitionWho>().Single();
                var target = targets.OfType<EWFieldDefinitionWho>().Single();
                Assert.AreEqual(source.AllowMultiple, target.AllowMultiple);
                Assert.AreEqual(source.SelectionMode, target.SelectionMode);
                Assert.AreEqual(source.SelectionSource, target.SelectionSource);
            }

            {
                foreach (var source in sources)
                {
                    var target = targets.Single(t => t.GetType() == source.GetType());
                    Assert.AreEqual(source.AllowNull, target.AllowNull);
                    Assert.AreEqual(source.IsUnique, target.IsUnique);
                    Assert.AreEqual(source.InternalName, target.InternalName);
                    Assert.AreEqual(source.DisplayName.ToText(), target.DisplayName.ToText());
                    Assert.AreEqual(source.Description.ToText(), target.Description.ToText());
                    Assert.AreEqual(source.CreatedBy, target.CreatedBy);
                    Assert.AreEqual(source.ModifiedBy, target.ModifiedBy);
                    Assert.AreEqual(source.Created, target.Created);
                    Assert.AreEqual(source.Modified, target.Modified);
                    CollectionAssert.AreEqual(source.Timestamp, target.Timestamp);
                }
            }
        }

        private async Task ValidateDatabaseColumnsAsync(IEnumerable<Tuple<string, string>> expectedColumns)
        {
            var actualColumns = (await GetDatabaseUserTablesAsync(this.TestDbContext.Database.Connection))
                .Single(p => p.Key.Equals("testcase_test"))
                .Value
                .Where(c => c.Item1.StartsWith("testcase_", StringComparison.OrdinalIgnoreCase));

            Assert.AreEqual(expectedColumns.Count(), actualColumns.Count());

            foreach (var expected in expectedColumns)
            {
                var actual = actualColumns.SingleOrDefault(c => StringComparer.OrdinalIgnoreCase.Compare(expected.Item1, c.Item1) == 0);
                Assert.IsNotNull(actual);
                Assert.AreEqual(expected.Item2, actual.Item2, true);
                Assert.IsTrue(actual.Item3);
            }
        }

        private static EWText CreateText()
        {
            var text = new EWText();
            text.Values.Add(new EWTextValue("en", "v1"));
            text.Values.Add(new EWTextValue("zh-Hans", "v2"));

            return text;
        }
    }
}
